#include "clkXmlDocument.h"

clkXmlDocument::clkXmlDocument()
{
	m_opened = false;
	m_root = 0;
}

clkXmlDocument::clkXmlDocument(string a_file)
{
	m_opened = false;
	m_root = 0;
	open(a_file);
}

clkXmlDocument::~clkXmlDocument()
{
	close();
}

#include <Mmsystem.h>
#pragma comment(lib,"Winmm.lib")

void clkXmlDocument::open(string m_file)
{
	//temp variables
	int timeStr = 0;
	int timeEnd = 0;
	float timeTot = 0;

	//check if already open; must close document before opening
	if(m_opened)
		return;

	//print title message
	printf("----------------------------------------------------------------\n");
	printf("XML Parsing: \"%s\"\n",m_file.c_str());
	printf("----------------------------------------------------------------\n");

	//open file
	string line;
	ifstream file;
	file.open(m_file);
	if(file.is_open())
	{
		timeStr = timeGetTime();
		printf("> Successfully opened file!\n");
		printf("> Reading from file...");
		while(file.good())
		{
			getline(file,line);
			m_source.append(line);
		}
		timeEnd = timeGetTime();
		timeTot += (timeEnd-timeStr)/1000.0f;
		printf("Done in %f seconds!\n",(timeEnd-timeStr)/1000.0f);
		file.close();
		m_opened = true;
	}
	else
	{
		printf("> Failed to open file: %s\n",m_file.c_str());
		m_opened = false;
		return;
	}
	
	printf("> Building from file...");
	timeStr = timeGetTime();

	//build tree
	int index = 0;
	m_root = new clkXmlElement;
	m_elements.push_back(m_root);
	build(m_root,index);

	timeEnd = timeGetTime();
	timeTot += (timeEnd-timeStr)/1000.0f;
	printf("Done in %f seconds!\n",(timeEnd-timeStr)/1000.0f);
	printf("> Took %f seconds to open!\n",timeTot);
	printf("----------------------------------------------------------------\n\n");
}

void clkXmlDocument::close()
{
	//flag doc closed
	m_opened = false;

	//clear file path and source
	m_root = 0;
	m_path.clear();
	m_source.clear();

	//delete memory from elements and its attributes
	for(int i=0;i<(int)m_elements.size();i++)
	{
		if(!m_elements[i]) continue;
		for(int j=0;j<(int)m_elements[i]->m_attributes.size();j++)
			delete m_elements[i]->m_attributes[j];
		m_elements[i]->m_attributes.clear();
		delete m_elements[i];
		m_elements[i] = 0;
	}
	m_elements.clear();
}

void clkXmlDocument::build(clkXmlElement* a_element, int& a_index)
{
	//temporary variables and flags
	bool read = false;
	bool readData = false;
	int elementIndex = 0;
	int elementType = CLK_XMLELEMENT_NONE;
	int elementAction = CLK_XMLELEMENT_NAME;
	clkXmlElement* element = a_element;
	clkXmlAttribute* attribute = 0;

	//loop through source text
	while(a_index < (int)m_source.size())
	{
		//cache chars
		char ch = m_source[a_index];
		char chP = (a_index-1 >= 0)?m_source[a_index-1]:0;
		char chN = (a_index+1 < (int)m_source.size())?m_source[a_index+1]:0;

		//check where to start reading then continue
		if(ch == '<')
		{
			read = true;
			if(elementAction != CLK_XMLELEMENT_DATA)
			{
				++a_index;
				continue;
			}
		}

		//continue if not reading yet or char is not TAB
		if(!read)
		{
			++a_index;
			continue;
		}

		//continue if TAB char detected
		if(ch == '\t')
		{
			++a_index;
			continue;
		}

		//check for element type
		switch(ch)
		{
		case '?':
			if(elementType == CLK_XMLELEMENT_NONE)
				elementType = CLK_XMLELEMENT_DECL;
			break;
		case '/':
			if(elementType == CLK_XMLELEMENT_NONE)
				elementType = CLK_XMLELEMENT_END;
			break;
		default:
			if(elementAction != CLK_XMLELEMENT_DATA)
				elementType = CLK_XMLELEMENT_START;
			break;
		}

		if(element->m_name.compare("float_array") == 0)
			int stop = 0;

		//determine what to do base on element type
		switch(elementType)
		{
		//element is a declaration tag
		case CLK_XMLELEMENT_DECL:
			{
				read = false;
				++a_index;
				continue;
			}
			break;
		//element is a end tag
		case CLK_XMLELEMENT_END:
			{
				++a_index;
				continue;
			}
			break;
		//element is a start tag
		case CLK_XMLELEMENT_START:
			{
				//determine what actions to build
				switch(elementAction)
				{
				//parse element name
				case CLK_XMLELEMENT_NAME:
					{
						if(ch == '>')
						{
							//next action is to parse element data
							elementAction = CLK_XMLELEMENT_DATA;
							++a_index;

							//determine if end of source
							if(a_index == (int)m_source.size())
							{
								//nullify reference to this element which will be deleted
								//m_elements[elementIndex] = 0;
								if(element->m_prev)
									element->m_prev->m_next = 0;
								//if(element->m_parent)
								//	element->m_parent->m_child = 0;
								//delete element;
								element = 0;
								
								//delete temp attribute
								delete attribute;
								attribute = 0;

								return;
							}

							continue;
						}
						else if(ch == ' ')
						{
							//create attribute
							attribute = new clkXmlAttribute;

							//next action is to parse attribute name
							elementAction = CLK_XMLELEMENT_ATTR_NAME;
							++a_index;
							continue;
						}
						else
						{
							//append char to name
							element->m_name += ch;
						}
					}
					break;
				//parse element data
				case CLK_XMLELEMENT_DATA:
					{
						if(ch == '<')
						{
							//add sibling
							if(chN == '/')
							{
								//check for end tag
								if(element && element->m_parent)
								{
									if(element->m_name == element->m_parent->m_name)
									{
										//nullify reference to this element which will be deleted
										//m_elements[elementIndex] = 0;
										if(element->m_prev)
											element->m_prev->m_next = 0;
										//if(element->m_parent)
										//	element->m_parent->m_child = 0;
										//delete element;
										element = 0;

										//delete temp attribute
										delete attribute;
										attribute = 0;

										return;
									}
								}

								//create and link sibling element
								element->m_next = new clkXmlElement;
								elementIndex = m_elements.size();
								m_elements.push_back(element->m_next);
								element->m_next->m_parent = element->m_parent;
								element->m_next->m_prev = element;
								element = element->m_next;

								//next action is to parse next element
								read = false;
								elementType = CLK_XMLELEMENT_NONE;
								elementAction = CLK_XMLELEMENT_NAME;
								++a_index;
								continue;
							}
							//add child
							else
							{
								//nullify reference to this element which will be deleted
								if(element && element->m_parent)
								{
									if(element->m_name == element->m_parent->m_name)
									{
										//nullify reference to this element which will be deleted
										//m_elements[elementIndex] = 0;
										if(element->m_prev)
											element->m_prev->m_next = 0;
										//if(element->m_parent)
										//	element->m_parent->m_child = 0;
										//delete element;
										element = 0;

										//delete temp attribute
										delete attribute;
										attribute = 0;

										return;
									}
								}

								//create and link sibling element
								element->m_child = new clkXmlElement;
								elementIndex = m_elements.size();
								m_elements.push_back(element->m_child);
								element->m_child->m_parent = element;

								//recursive build for child
								build(element->m_child,a_index);

								//check for end of source
								if(a_index == (int)m_source.size())
									return;

								//create and link sibling element
								element->m_next = new clkXmlElement;
								elementIndex = m_elements.size();
								m_elements.push_back(element->m_next);
								element->m_next->m_parent = element->m_parent;
								element->m_next->m_prev = element;
								element = element->m_next;

								//next action is to parse next element
								//w/o index increment because start tag is current
								read = false;
								elementType = CLK_XMLELEMENT_NONE;
								elementAction = CLK_XMLELEMENT_NAME;
								continue;
							}
						}
						else
						{
							//append char to data
							element->m_data += ch;
						}
					}
					break;
				//parse attribute name
				case CLK_XMLELEMENT_ATTR_NAME:
					{
						if(ch == ' ')
						{
							//dont account space
							++a_index;
							continue;
						}
						else if(ch == '=')
						{
							//next action is to parse 
							elementAction = CLK_XMLELEMENT_ATTR_DATA;
							++a_index;
							continue;
						}
						else
						{
							//append char to name
							attribute->m_name += ch;
						}
					}
					break;
				//parse attribute data
				case CLK_XMLELEMENT_ATTR_DATA:
					{
						//check for start quote to begin reading data
						if((ch == '\"' || ch == '\'') && !readData)
						{
							readData = true;
							++a_index;
							continue;
						}
						//check for closing quote to end reading data
						else if((ch == '\"' || ch == '\'') && readData)
						{
							readData = false;
							++a_index;
							continue;
						}
						else if(readData)
						{
							//append char to data
							attribute->m_data += ch;
						}
						else if(ch == '>' && !readData)
						{
							//add attribute to list and nullify
							element->m_attributes.push_back(attribute);
							attribute = 0;

							//next action is to parse element data
							elementAction = CLK_XMLELEMENT_DATA;
							++a_index;
							continue;
						}
						else if(ch == ' ' && !readData)
						{
							//add attribute to list and create anew
							element->m_attributes.push_back(attribute);
							attribute = new clkXmlAttribute;

							//next action is to parse next attribute
							elementAction = CLK_XMLELEMENT_ATTR_NAME;
							++a_index;
							continue;
						}
						else if(ch == '/' && !readData)
						{
							//add attribute to list and nullify
							element->m_attributes.push_back(attribute);
							attribute = 0;

							//create and link sibling element
							element->m_next = new clkXmlElement;
							elementIndex = m_elements.size();
							m_elements.push_back(element->m_next);
							element->m_next->m_parent = element->m_parent;
							element->m_next->m_prev = element;
							element = element->m_next;

							//next action is to parse next element
							read = false;
							elementType = CLK_XMLELEMENT_NONE;
							elementAction = CLK_XMLELEMENT_NAME;
							++a_index;
							continue;
						}
						else
						{
							continue;
						}
					}
					break;
				}
			}
			break;
		}
		++a_index;
	}
}